package parsers;

import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;
import gui.MainFrame;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.mail.MessagingException;
import notifiers.MailBuilder;
import notifiers.MailSender;
import notifiers.SmsSender;
import org.horrabin.horrorss.RssChannelBean;
import org.horrabin.horrorss.RssFeed;
import org.horrabin.horrorss.RssItemBean;
import org.horrabin.horrorss.RssParser;
import rsshunter.RSSBase;
import rsshunter.RSSChannel;
import rsshunter.RSSEntry;
import rsshunter.RSSKeyword;

/**
 * Concrete parser class used for parsing Rss Feeds.
 */
public class RSSParser extends AbstractParser {

    private RssParser rss = new RssParser(); //DO NOT DELETE! It's not RSSParser, just similar name

    public RSSParser(MainFrame parent, MailSender mailSender,
            SmsSender smsSender) {
        super(parent, mailSender, smsSender);
    }

    @Override
    protected void parse(RSSBase base) {
        Map<String, Integer> channelsMap =
                new HashMap<String, Integer>();
        List<RSSChannel> channels = getChannels(base);
        List<RSSEntry> entries;

        if (!channels.isEmpty()) {
            progressStep = (int) Math.floor(100 / channels.size() / 4);
        }

        // Get new feeds
        for (RSSChannel channel : channels) {
            List<RSSKeyword> keywords = getKeywords(base);
            incrementProgress();
            try {
                entries = getNewFeeds(channel);
            } catch (Exception e) {
                parentFrame.setStatus(
                        "Couldn't get feeds from " + channel.getURI());
                continue;
            }

            if (!keywords.isEmpty()) {
                entries = checkEntries(entries, keywords);
            }
            if (!entries.isEmpty()) {
                channelsMap.put(channel.getURI(), entries.size());
            }
            saveEntries(entries);
        }

        notifyUser(channelsMap, base);
    }

    /**
     * Get new feeds from specific channel.
     *
     * @param channel Rss channel.
     * @return List of new feeds.
     */
    private List<RSSEntry> getNewFeeds(RSSChannel channel) throws Exception {
        List<RSSEntry> result = new LinkedList<RSSEntry>();
        RssFeed feed = rss.load(channel.getURI());
        incrementProgress();

        // Gets the channel information of the feed
        RssChannelBean ch = feed.getChannel();
        channel.setTitle(ch.getTitle());
        String lastModified = channel.getLastModified();
        String channelPubDate = ch.getPubDate().toString();
        incrementProgress();

        // Check if feeds are new
        if (channelPubDate == null
                || lastModified == null
                || !lastModified.equals(channelPubDate)) {

            // Get and iterate the items of the feed
            List<RssItemBean> items = feed.getItems();
            result = parseFeeds(items, channel);

            // Save new channel properties
            channel.setLastModified(channelPubDate);
            db.updateChannel(channel);
        }
        incrementProgress();
        return result;
    }

    /**
     * Parsing entries from RSSHorror to RSSHunter classes.
     *
     * @param rssHorrorEntries Entries from RSSHorror.
     * @param from Channel whereout entries come from.
     * @return List of RSSHunter entries.
     */
    private List<RSSEntry> parseFeeds(List<RssItemBean> rssHorrorEntries,
            RSSChannel from) {
        List<RSSEntry> result = new LinkedList<RSSEntry>();
        for (RssItemBean item : rssHorrorEntries) {
            RSSEntry actual = new RSSEntry("", item.getTitle(),
                    item.getLink(),
                    item.getDescription(),
                    item.getPubDate().toString(),
                    false,
                    from.getId());
            result.add(actual);
        }
        return result;
    }

    /**
     * Saves entries to database.
     *
     * @param entries List of RSS entries.
     */
    private void saveEntries(List<RSSEntry> entries) {
        db.addEntries(entries);
    }

    /**
     * Notifies user about new feeds.
     *
     * @param channelsMap Map containing number of new entries in each channel
     */
    private void notifyUser(Map<String, Integer> channelsMap, RSSBase base) {
        if (channelsMap.isEmpty()) {
            //Nothing to notify
            return;
        }

        String notifyType = base.getNotify();
        if (notifyType.equals("E-mail")) {

            // Prepare message
            String message = MailBuilder.composeRssNotification(
                    channelsMap, base.getName());
            String subject = "RSSHunter - you've got new entries";
            try {

                // Send message
                mailSender.sendMail(subject, message);
            } catch (MessagingException ex) {
                displayAuthenticationError(
                        "Couldn't send e-mail notification for base "
                        + base.getName() + " : Bad login or password.",
                        "Bad gmail login or password");
            }

        } else if (notifyType.equals("Sms")) {

            // Prepare message
            String message =
                    smsSender.composeRssNotification(channelsMap, base.getName());
            try {

                //Send message
                smsSender.sendSMS(message);
            } catch (AuthenticationException e) {
                displayAuthenticationError(
                        "Couldn't send SMS notification for base "
                        + base.getName() + " : Bad login or password.",
                        "Bad gmail login or password");
            } catch (ServiceException e) {
                System.err.println("Error sending sms notification "
                        + "for base " + base + ": " + e);
            }
        }
        parentFrame.displayTrayMessage("RSSHunter - New entries found",
                "There are new entries in base " + base.getName() + ".");
    }

    /**
     * Check if given entries match keywords.
     *
     * @param entries List of entries to check.
     * @param keywords List of keywords. At least one must match for entry to
     * pass.
     * @return List of matching entries.
     */
    private List<RSSEntry> checkEntries(List<RSSEntry> entries,
            List<RSSKeyword> keywords) {
        List<RSSEntry> result = new LinkedList<RSSEntry>();
        Matcher matcher;
        Pattern pattern = createPattern(keywords);

        // Check entries
        for (RSSEntry entry : entries) {
            matcher = pattern.matcher(entry.getDescription());
            if (matcher.find()) {
                result.add(entry);
            }
        }

        return result;
    }

    /**
     * Integrate keywords into regex pattern
     *
     * @return Pattern containing all keywords.
     */
    private Pattern createPattern(List<RSSKeyword> keywords) {
        StringBuilder bigPattern = new StringBuilder();
        for (RSSKeyword keyword : keywords) {
            bigPattern.append("(");
            bigPattern.append(keyword.getValue());
            bigPattern.append(")|");
        }
        bigPattern.deleteCharAt(bigPattern.length() - 1);
        return Pattern.compile(bigPattern.toString(),Pattern.CASE_INSENSITIVE);
    }
}